/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ru.niir.goodfil.ui;

import org.apache.log4j.Logger;
import ru.niir.goodfil.audit.domain.GoodUIEvent;
import ru.niir.goodfil.audit.domain.enums.Crud;
import ru.niir.goodfil.db.Brand;
import ru.niir.goodfil.db.Good;
import ru.niir.goodfil.db.GoodType;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.ui.utils.PageIndex;
import ru.niir.goodfil.utils.validation.CreateCheckResult;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Request бин для страницы товаров
 */
public class GoodsBean extends PageBean {
    private static Logger logger = Logger.getLogger(GoodsBean.class);

    /**
     * Название настройки, задающей размер страницы
     */
    private static final String GOODS_PAGE_SIZE_KEY = ".goods.page.size";
    /**
     * Название настройки, задающей отступ от текущей страницы влево для пэйджинатора
     */
    private static final String GOODS_LEFT_PAGE_OFFSET_SIZE = ".goods.page.left.offset";
    /**
     * Название настройки, задающей отступ от текущей страницы вправо для пэйджинатора
     */
    private static final String GOODS_RIGHT_PAGE_OFFSET_SIZE = ".goods.page.right.offset";

    /**
     * Количество страниц (с учетом фильтра)
     */
    private int pagesCount = -1;
    /**
     * Количество товаров (с учетом фильтра)
     */
    private int goodsCount = -1;
    /**
     * Размер страницы
     */
    private int pageSize = -1;
    /**
     * Отступ от текущей страницы влево для паджинатора (кол-во страниц, слева от текущей)
     */
    private int _leftPageOffset = -1;
    /**
     * Отступ от текущей страницы вправо для паджинатора (кол-во страниц, справа от текущей)
     */
    private int _rightPageOffset = -1;

    /**
     * Идентификатор товара
     */
    private long goodId = -1;

    /**
     * Идентификатор перемещаемого товара
     */
    private long moveGoodTypeId;

    /**
     * Возвращает (согласно настройкам) размер страницы (для пэйджинатора)
     *
     * @return размер страници
     */
    public int getPageSize() {
        gf().createIfNotExists(GOODS_PAGE_SIZE_KEY, 10);
        if (pageSize == -1) pageSize = gf().getInt(GOODS_PAGE_SIZE_KEY);
        return pageSize;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа влево (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getLeftPaggeOffset() {
        gf().createIfNotExists(GOODS_LEFT_PAGE_OFFSET_SIZE, 5);
        if (_leftPageOffset == -1) _leftPageOffset = gf().getInt(GOODS_LEFT_PAGE_OFFSET_SIZE);
        return _leftPageOffset;
    }

    /**
     * Возвращает (согласно настройкам) размер отступа вправо (в страницах) для пэйджинатора
     *
     * @return размер отступа
     */
    public int getRightPaggeOffset() {
        gf().createIfNotExists(GOODS_RIGHT_PAGE_OFFSET_SIZE, 5);
        if (_rightPageOffset == -1) _rightPageOffset = gf().getInt(GOODS_RIGHT_PAGE_OFFSET_SIZE);
        return _rightPageOffset;
    }

    /**
     * Возвращает количество товаров (с учетом фильтра)
     *
     * @return количество товаров
     */
    public int getGoodsCount() {
        if (goodsCount == -1) {
            goodsCount = (int) gf().getGoodsCount(getSessionBean().getGoodsFilter());
        }
        return goodsCount;
    }

    /**
     * Возвращает количество страниц (с учетом фильтра)
     *
     * @return количество страниц
     */
    public int getPagesCount() {
        if (pagesCount == -1) {
            pagesCount = getGoodsCount() / getPageSize() + 1;
        }
        return pagesCount;
    }

    /**
     * Перейти к началу (на первую страницу)
     */
    public void toBegin() {
        applyPage();
        setPage(1);
    }

    /**
     * Перейти к концу (на последнюю страницу)
     */
    public void toEnd() {
        applyPage();
        setPage(getPagesCount());
    }

    /**
     * Отступить на несколько страниц назад
     */
    public void longPrevPage() {
        applyPage();
        int p = getPage() - getLeftPaggeOffset() - 1;
        if (p < 1) p = 1;
        setPage(p);
    }

    /**
     * Отступить на несколько страниц вперед
     */
    public void longNextPage() {
        applyPage();
        int p = getPage() + getRightPaggeOffset() + 1;
        if (p > getPagesCount()) p = getPagesCount();
        setPage(p);
    }

    /**
     * Перейти на страницу вперед
     */
    public void nextPage() {
        applyPage();
        if (getPage() < getPagesCount()) setPage(getPage() + 1);
    }

    /**
     * Перейти на страницу назад
     */
    public void prevPage() {
        applyPage();
        if (getPage() > 1) setPage(getPage() - 1);
    }

    /**
     * Возвращает номер текущей страницы
     */
    public int getPage() {
        return getSessionBean().getGoodsPage();
    }

    /**
     * Установить номер текущей страницы
     */
    public void setPage(int page) {
        getSessionBean().setGoodsPage(page);
    }

    /**
     * Применить текущую страницу
     */
    public void applyPage() {
        goodsCount = -1;
        pagesCount = -1;
    }

    /**
     * Возвращает номера страниц для отрисовки в пэйджинаторе
     *
     * @return список страниц
     */
    public List<PageIndex> getPageIndexes() throws ValidationException {
        int minPage = getPage() - getLeftPaggeOffset();
        if (minPage < 1) minPage = 1;

        int maxPage = minPage + getLeftPaggeOffset() + getRightPaggeOffset();
        if (maxPage > getPagesCount()) maxPage = getPagesCount();

        List<PageIndex> result = new ArrayList<PageIndex>();
        for (int i = minPage; i <= maxPage; i++) {
            result.add(new PageIndex(i, i == getPage()));
        }

        return result;
    }

    /**
     * Применить фильтр
     */
    public void applyFilter() {
        setPage(1);
        goodsCount = -1;
        pagesCount = -1;
    }

    /**
     * Возвращает номер первой страницы, которую необходимо отрисовать в пэйджинаторе
     *
     * @return номер первой страници
     */
    private int getPageStart() throws ValidationException {
        int pageStart = getPage() * getPageSize() - getPageSize();
        if (pageStart < 0) pageStart = 0;
        return pageStart;
    }

    /**
     * Возвращает номер последней страницы, которую необходимо отрисовать в пэйджинаторе
     *
     * @return номер последней страници
     */
    private int getPageEnd() throws ValidationException {
        int pageEnd = getPageStart() + getPageSize();
        if (pageEnd > getGoodsCount()) pageEnd = getGoodsCount();
        return pageEnd;
    }

    /**
     * Возвращает перечень товаров для отображения, с учетом фильтра и номера текущей страницы
     *
     * @return список товаров
     */
    public List<Good> getGoods() throws ValidationException {
        List<GoodType> goodTypes = gf().getGoodTypes();
        List<Good> goods = gf().getGoods(getPageStart(), getPageSize(), getSessionBean().getGoodsFilter());
        List<Good> result = new ArrayList<Good>();
        for (Good good : goods) {
            if (good.isDeleted()) {
                continue;
            }

            for (GoodType goodType : goodTypes) {
                if (goodType.getId() == good.getGoodTypeId()) {
                    good.setGoodType(goodType);
                }
            }

            //  Получение данных о том, выбран ли данный товар
            good.setSelected(getSessionBean().getSelectedGoods().contains(good.getId()));

            result.add(good);
        }
        return result;
    }

    /**
     * Установить идентификатор текущего товара (для операций редактирование и удаление)
     *
     * @param goodId идентификатор
     */
    public void setGoodId(long goodId) {
        this.goodId = goodId;
    }

    /**
     * Возвращает идентификатор текущего товара (для операций редактирование и удаление)
     *
     * @return идентификатор
     */
    public long getGoodId() {
        return goodId;
    }

    /**
     * Возвращает идентификатор перемещаемого товара.
     *
     * @return идентификатор
     */
    public long getMoveGoodTypeId() {
        return moveGoodTypeId;
    }

    /**
     * Устанавливает идентификатор перемещаемого товара.
     *
     * @param moveGoodTypeId идентификатор
     */
    public void setMoveGoodTypeId(long moveGoodTypeId) {
        this.moveGoodTypeId = moveGoodTypeId;
    }

    /**
     * Возвращает
     *
     * @return
     */
    public int getSelectedGoodsCount() {
        return getSessionBean().getSelectedGoods().size();
    }

    /**
     * Построить дерво групп товаров и вернуть его корневой узел ("Корневая группа")
     */
    private GoodType buildGoodTypeTree() {
        List<GoodType> nodesList = getSessionBean().getGoodfil().getGoodTypes();

        for (GoodType gt : nodesList) gt.getChildren().clear();

        GoodType rootGroodType = null;
        for (int i = 0; i < nodesList.size(); i++) {
            GoodType n1 = nodesList.get(i);
            if (n1.getId() == 1) rootGroodType = n1;

            for (int j = 0; j < nodesList.size(); j++) {
                if (i == j) continue;

                GoodType n2 = nodesList.get(j);
                if (n2.getParentId() == n1.getId()) {
                    n1.getChildren().add(n2);
                    n2.setParent(n1);
                }
            }
        }

        rootGroodType.setLevel(0);

        return rootGroodType;
    }

    public Good getGood() {
        return getSessionBean().getEditableGood();
    }

    /**
     * Добавить новый товар.
     */
    public void addGood() {
        Good good = getSessionBean().getEditableGood();

        if (good.getGoodTypeId() == -1) {
            facesError("Необходимо выбрать группу товаров");
        }

        if (hasFacesErrors()) return;

        if (good.getId() < 1) {
            try {
                gf().saveGood(good);
                getAuditService().logGoodUIEvent(prepareGoodUIEvent(good, Crud.CREATE));
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors()) {
                    facesError(error.toHumanString());
                }
            } catch (CreateFailureException ex) {
                logger.error(ex);
                ex.printStackTrace();
                CreateCheckResult createCheckResult = ex.getResult();
                facesError(createCheckResult.getMsg());
            }
        } else {
            try {
                gf().updateGood(good);
                getAuditService().logGoodUIEvent(prepareGoodUIEvent(good, Crud.UPDARE));
                facesInfo("Товар изменен");
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors()) {
                    facesError(error.toHumanString());
                }
            }
        }
    }

    private GoodUIEvent prepareGoodUIEvent(Good good, Crud crud) {
        GoodUIEvent event = getAuditHelper().createEvent(GoodUIEvent.class);
        event.setCode(good.getCode());
        event.setCrud(crud);
        event.setName(good.getName());
        event.setVolume(String.valueOf(good.getVolume()));
        event.setWeight(String.valueOf(good.getWeight()));

        return event;
    }

    /**
     * Удаление данных о товаре
     */
    public void removeGood() {
        Good good = gf().getGoodById(goodId);
        gf().softRemoveGoodById(goodId);
        getAuditService().logGoodUIEvent(prepareGoodUIEvent(good, Crud.DELETE));
    }

    /**
     * Добавить товар в прейскурант
     */
    public void unremoveGood() {
        gf().softUnRemoveGoodById(goodId);
    }

    /**
     * Перемещение товара.
     */
    public void moveGoods() {
        try {
            for (long gId : getSessionBean().getSelectedGoods()) {
                try {
                    Good g = gf().getGoodById(gId);
                    g.setGoodTypeId(moveGoodTypeId);
                    gf().updateGood(g);
                    facesInfo("Товар обновлен");
                } catch (ValidationException ex) {
                    logger.error(ex);
                    ex.printStackTrace();
                    ValidationResult validationResult = ex.getResult();
                    for (ValidationError error : validationResult.getErrors()) {
                        facesError(error.toHumanString());
                    }
                    facesError("Не удалось переместить товар: " + gId);
                }
            }

            facesInfo("Товары (" + getSessionBean().getSelectedGoods().size() + " шт.) были перемещены");
        } finally {
            getSessionBean().getSelectedGoods().clear();
        }
    }


    /**
     * -------------------------------------------------------------------------
     * ----------Валидаторы----------
     * -------------------------------------------------------------------------
     */

    /**
     * UNSAFE Пиздец. И на это ты тратишь время????
     * Плохая идея. Т.к. правлю баги то  не буду трогать.
     *
     * @param context
     * @param component
     * @param obj
     * @param msg
     */
    private void validatePositiveFloat(FacesContext context, UIComponent component, Object obj, String msg) {
        try {
            logger.debug("msg: " + obj.toString());
            float value;
            if (obj instanceof Float) {
                value = (Float) obj;
            } else if (obj instanceof Double) {
                value = ((Double) obj).floatValue();
            } else {
                value = ((Long) obj).floatValue();
            }

            logger.debug("msg: " + msg + ", " + value);
            if (value >= 0) return;
        } catch (Exception e) {
            e.printStackTrace();
        }

        facesError(context, component, msg);
    }

    /**
     * Проверка: положительное число
     */
    public void validateWeight(FacesContext context, UIComponent component, Object obj) {
        validatePositiveFloat(context, component, obj, "Вес должен быть положительным числом");
    }

    /**
     * Проверка: положительное число
     */
    public void validateVolume(FacesContext context, UIComponent component, Object obj) {
        validatePositiveFloat(context, component, obj, "Объем должены быть положительным числом");
    }

    /**
     * Проверка: положительное целое число

    public void validatePackageCount(FacesContext context, UIComponent component, Object obj) {
        //  То, что число должно быть целым проверяется ранее, в конвертере
        validatePositiveFloat(context, component, obj, "Количество шт. должно быть положительным числом");
    }
     */
    public void validatePriceCount(FacesContext context, UIComponent component, Object obj) {
        //  То, что число должно быть целым проверяется ранее, в конвертере
        validatePositiveFloat(context, component, obj, "Цена должна быть положительной");
    }

    private Map<Long, String> brandsMap = initBrandsMap();

    private Map<Long, String> initBrandsMap() {
        Map<Long, String> brandsMap = new HashMap<Long, String>();
        brandsMap.put(null, "Не установлен");
        for (Brand brand : gf().getBrands()) {
            brandsMap.put(brand.getId(), brand.getName());
        }
        return brandsMap;
    }

    public Map<Long, String> getBrandsMap() {
        return brandsMap;
    }

    public void setBrandsMap(Map<Long, String> brandsMap) {
        this.brandsMap = brandsMap;
    }
}